#include "priority_queue.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define ASSERT(expr) do { \
if (!(expr)) {\
printf("\nAssertion failed %s (%s:%d).\n", #expr, __FILE__, __LINE__); \
return false; \
}\
else {\
		printf("."); \
} \
}while(0)

#define RUN_TEST(test) do { \
	printf("Running "#test); \
if (test()) {\
printf("[OK]\n"); \
} \
} while (0)

#define ASSERT_NO_CRASH(expr) expr; ASSERT(true)
#define ASSERT_EQUALS(expr,expected) ASSERT((expr) == (expected))
#define ASSERT_NOT_EQUALS(expr,unexpected) ASSERT((expr) != (unexpected))
#define ASSERT_STRING_EQUALS(s1,s2) ASSERT(strcmp(s1, s2) == 0)

#define ASSERT_SUCCESS(expr) ASSERT_EQUALS(expr, PRIORITY_QUEUE_SUCCESS)
#define ASSERT_NULL_ARGUMENT(expr) \
		ASSERT_EQUALS(expr, PRIORITY_QUEUE_NULL_ARGUMENT)
#define ASSERT_EMPTY(expr) ASSERT_EQUALS(expr, PRIORITY_QUEUE_EMPTY)
#define ASSERT_TRUE(expr) ASSERT_EQUALS(expr, true)
#define ASSERT_FALSE(expr) ASSERT_EQUALS(expr, false)
#define ASSERT_NULL(expr) ASSERT_EQUALS(expr, NULL)
#define ASSERT_NOT_NULL(expr) ASSERT_NOT_EQUALS(expr, NULL)
 
	static PriorityQueueElement stringCopy(PriorityQueueElement str) {
		assert(str);
		char* result = malloc(strlen((char*)str)+1);
		return result ? strcpy(result,(char*)str) : NULL;
	}

	static void stringDestroy(PriorityQueueElement str) {
		free((char*)str);
	}

	static void setUp(PriorityQueue pq){
		priorityQueueAdd(pq,"first",1);
		priorityQueueAdd(pq,"second",2);
		priorityQueueAdd(pq,"third",3);
		priorityQueueAdd(pq,"fourth",4);
#define NUMBER_OF_ELEMENTS 4
	}

	static bool testCreateAndDestroy() {
	PriorityQueue pq=priorityQueueCreate(stringCopy,stringDestroy);
	ASSERT_NOT_NULL(pq);
	ASSERT_NULL(priorityQueueCreate(stringCopy,NULL));
	ASSERT_NULL(priorityQueueCreate(NULL,stringDestroy));
	ASSERT_NO_CRASH(priorityQueueDestroy(NULL));
	ASSERT_NO_CRASH(priorityQueueDestroy(pq));
	return true;
	}


	static bool testCopy() {
		ASSERT_NULL(priorityQueueCopy(NULL));
		PriorityQueue pq=priorityQueueCreate(stringCopy,stringDestroy);
		ASSERT_NOT_NULL(pq);
		PriorityQueue pq2=priorityQueueCopy(pq);
		ASSERT_NOT_NULL(pq2);
		ASSERT_EQUALS(priorityQueueGetSize(pq2),0);
		setUp(pq);
		PriorityQueue pq3=priorityQueueCopy(pq);
		ASSERT_NOT_NULL(pq3);
		ASSERT_EQUALS(priorityQueueGetSize(pq3),priorityQueueGetSize(pq));
		while(priorityQueueGetSize(pq)>0) {
			ASSERT_STRING_EQUALS(priorityQueueTop(pq),priorityQueueTop(pq3));
			priorityQueuePop(pq);
			priorityQueuePop(pq3);
		}

		priorityQueueDestroy(pq3);
		priorityQueueDestroy(pq2);
		priorityQueueDestroy(pq);
		return true;
	}

	static bool testAdd() {
		PriorityQueue pq=priorityQueueCreate(stringCopy,stringDestroy);
		ASSERT_NULL_ARGUMENT(priorityQueueAdd(pq,NULL,5));
		ASSERT_NULL_ARGUMENT(priorityQueueAdd(NULL,"blabla",1));
		ASSERT_SUCCESS(priorityQueueAdd(pq,"blabla",1));
		ASSERT_STRING_EQUALS(priorityQueueTop(pq),"blabla");
		ASSERT_SUCCESS(priorityQueueAdd(pq,"foo",0));
		ASSERT_STRING_EQUALS(priorityQueueTop(pq),"blabla");
		ASSERT_SUCCESS(priorityQueueAdd(pq,"bar",1));
		ASSERT_STRING_EQUALS(priorityQueueTop(pq),"blabla");
		ASSERT_SUCCESS(priorityQueueAdd(pq,"top",2));
		ASSERT_STRING_EQUALS(priorityQueueTop(pq),"top");
		priorityQueueDestroy(pq);
		return true;
	}


	static bool testClearAndSize() {
		PriorityQueue pq=priorityQueueCreate(stringCopy,stringDestroy);
		ASSERT_EQUALS(priorityQueueGetSize(pq),0);
		setUp(pq);
		ASSERT_EQUALS(priorityQueueGetSize(pq),NUMBER_OF_ELEMENTS);
		ASSERT_SUCCESS(priorityQueueClear(pq));
		ASSERT_EQUALS(priorityQueueGetSize(pq),0);
		ASSERT_EQUALS(priorityQueueGetSize(NULL),-1);
		ASSERT_NULL_ARGUMENT(priorityQueueClear(NULL));
		priorityQueueDestroy(pq);

		return true;
	}


	static bool testPop() {
		PriorityQueue pq=priorityQueueCreate(stringCopy,stringDestroy);
		ASSERT_EMPTY(priorityQueuePop(pq));
		setUp(pq);
		ASSERT_SUCCESS(priorityQueuePop(pq));
		ASSERT_STRING_EQUALS(priorityQueueTop(pq),"third");
		ASSERT_EQUALS(priorityQueueGetSize(pq),NUMBER_OF_ELEMENTS-1);
		ASSERT_NULL_ARGUMENT(priorityQueuePop(NULL));
		priorityQueueDestroy(pq);

		return true;
	}


	static bool testTop() {
		PriorityQueue pq=priorityQueueCreate(stringCopy,stringDestroy);
		ASSERT_NULL(priorityQueueTop(pq));
		setUp(pq);
		ASSERT_STRING_EQUALS(priorityQueueTop(pq),"fourth");
		ASSERT_EQUALS(priorityQueueGetSize(pq),NUMBER_OF_ELEMENTS);
		ASSERT_NULL(priorityQueueTop(NULL));
		priorityQueueDestroy(pq);
		return true;
	}

static bool filterFunc(PriorityQueueElement str){
	assert(str !=NULL);
	return strcmp((char*)str, "third") == 0;
}

static bool longStringFilter(PriorityQueueElement str) {
	assert(str);
	return strlen(str)>5;
}


	static bool testFilter() {
		PriorityQueue pq=priorityQueueCreate(stringCopy,stringDestroy);

		PriorityQueue pq3=priorityQueueFilter(pq,longStringFilter);
		ASSERT_NOT_NULL(pq3);
		ASSERT_EQUALS(priorityQueueGetSize(pq3),0);

		setUp(pq);

		PriorityQueue pq4=priorityQueueFilter(pq,longStringFilter);
		ASSERT_EQUALS(priorityQueueGetSize(pq4),2);
		ASSERT_STRING_EQUALS(priorityQueueTop(pq4),"fourth");
		priorityQueuePop(pq4);
		ASSERT_STRING_EQUALS(priorityQueueTop(pq4),"second");

		PriorityQueue pq2=priorityQueueFilter(pq,filterFunc);
		priorityQueuePop(pq);
		ASSERT_STRING_EQUALS(priorityQueueTop(pq),priorityQueueTop(pq2));

		ASSERT_NULL(priorityQueueFilter(NULL,longStringFilter));
		ASSERT_NULL(priorityQueueFilter(pq,NULL));

		priorityQueueDestroy(pq);
		priorityQueueDestroy(pq2);
		priorityQueueDestroy(pq3);
		priorityQueueDestroy(pq4);
		return true;
	}

	int main() {

		RUN_TEST(testCreateAndDestroy);
		RUN_TEST(testCopy);
		RUN_TEST(testAdd);
		RUN_TEST(testClearAndSize);
		RUN_TEST(testPop);
		RUN_TEST(testTop);
		RUN_TEST(testFilter);

		return 0;
	}
